<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Cursor MCP Plugin</title>
    <style>
      body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          Helvetica, Arial, sans-serif;
        margin: 0;
        padding: 20px;
        color: #e0e0e0;
        background-color: #1e1e1e;
      }
      .container {
        display: flex;
        flex-direction: column;
        height: 100%;
      }
      h1 {
        font-size: 16px;
        font-weight: 600;
        margin-bottom: 10px;
        color: #ffffff;
      }
      h2 {
        font-size: 14px;
        font-weight: 600;
        margin-top: 20px;
        margin-bottom: 8px;
        color: #ffffff;
      }
      button {
        background-color: #18a0fb;
        border: none;
        color: white;
        padding: 8px 12px;
        border-radius: 6px;
        margin-top: 8px;
        margin-bottom: 8px;
        cursor: pointer;
        font-size: 14px;
        transition: background-color 0.2s;
      }
      button:hover {
        background-color: #0d8ee0;
      }
      button.secondary {
        background-color: #3d3d3d;
        color: #e0e0e0;
      }
      button.secondary:hover {
        background-color: #4d4d4d;
      }
      button:disabled {
        background-color: #333333;
        color: #666666;
        cursor: not-allowed;
      }
      input {
        border: 1px solid #444444;
        border-radius: 4px;
        padding: 8px;
        margin-bottom: 12px;
        font-size: 14px;
        width: 100%;
        box-sizing: border-box;
        background-color: #2d2d2d;
        color: #e0e0e0;
      }
      label {
        display: block;
        margin-bottom: 4px;
        font-size: 12px;
        font-weight: 500;
        color: #cccccc;
      }
      .status {
        margin-top: 16px;
        padding: 12px;
        border-radius: 6px;
        font-size: 14px;
      }
      .status.connected {
        background-color: #1a472a;
        color: #4ade80;
      }
      .status.disconnected {
        background-color: #471a1a;
        color: #ff9999;
      }
      .status.info {
        background-color: #1a3147;
        color: #66b3ff;
      }
      .section {
        margin-bottom: 24px;
      }
      .hidden {
        display: none;
      }
      .logo {
        width: 50px;
        height: 50px;
      }
      .header {
        display: flex;
        align-items: center;
        margin-bottom: 16px;
      }
      .header-text {
        margin-left: 12px;
      }
      .header-text h1 {
        margin: 0;
        font-size: 16px;
      }
      .header-text p {
        margin: 4px 0 0 0;
        font-size: 12px;
        color: #999999;
      }
      .tabs {
        display: flex;
        border-bottom: 1px solid #444444;
        margin-bottom: 16px;
      }
      .tab {
        padding: 8px 16px;
        cursor: pointer;
        font-size: 14px;
        font-weight: 500;
        color: #999999;
      }
      .tab.active {
        border-bottom: 2px solid #18a0fb;
        color: #18a0fb;
      }
      .tab-content {
        display: none;
      }
      .tab-content.active {
        display: block;
      }
      .link {
        color: #18a0fb;
        text-decoration: none;
        cursor: pointer;
      }
      .link:hover {
        text-decoration: underline;
      }
      .header-logo {
        padding: 16px;
        border-radius: 16px;
        background-color: #333;
      }
      .header-logo-image {
        width: 24px;
        height: 24px;
        object-fit: contain;
      }
      /* Progress styles */
      .operation-complete {
        color: #4ade80;
      }
      .operation-error {
        color: #ff9999;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="header">
        <div class="header-logo">
          <img
            class="header-logo-image"
            src=""
          />
        </div>
        <div class="header-text">
          <h1>Cursor Talk To Figma Plugin</h1>
          <p>Connect Figma to Cursor AI using MCP</p>
        </div>
      </div>

      <div class="tabs">
        <div id="tab-connection" class="tab active">Connection</div>
        <div id="tab-about" class="tab">About</div>
      </div>

      <div id="content-connection" class="tab-content active">
        <div class="section">
          <label for="port">WebSocket Server Port</label>
          <div style="display: flex; gap: 8px">
            <input
              type="number"
              id="port"
              placeholder="3055"
              value="3055"
              min="1024"
              max="65535"
            />
            <button id="btn-connect" class="primary">Connect</button>
          </div>
        </div>

        <div id="connection-status" class="status disconnected">
          Not connected to Cursor MCP server
        </div>

        <div class="section">
          <button id="btn-disconnect" class="secondary" disabled>
            Disconnect
          </button>
        </div>
        
        <!-- Add Progress Bar Section -->
        <div id="progress-container" class="section hidden">
          <h2>Operation Progress</h2>
          <div id="progress-message">No operation in progress</div>
          <div style="width: 100%; background-color: #444; border-radius: 4px; margin-top: 8px;">
            <div id="progress-bar" style="width: 0%; height: 8px; background-color: #18a0fb; border-radius: 4px; transition: width 0.3s;"></div>
          </div>
          <div style="display: flex; justify-content: space-between; margin-top: 4px; font-size: 12px;">
            <div id="progress-status">Not started</div>
            <div id="progress-percentage">0%</div>
          </div>
        </div>
      </div>

      <div id="content-about" class="tab-content">
        <div class="section">
          <h2>About Cursor Talk To Figma Plugin</h2>
          <p>
            This plugin allows Cursor AI to communicate with Figma, enabling
            AI-assisted design operations.
            <a
              class="link"
              onclick="window.open(`https://github.com/grab/cursor-talk-to-figma-mcp`, '_blank')"
              >Github</a
            >
          </p>
          <p>Version: 1.0.0</p>

          <h2>How to Use</h2>
          <ol>
            <li>Make sure the MCP server is running in Cursor</li>
            <li>Connect to the server using the port number (default: 3055)</li>
            <li>Once connected, you can interact with Figma through Cursor</li>
          </ol>
        </div>
      </div>
    </div>

    <script>
      // WebSocket connection state
      const state = {
        connected: false,
        socket: null,
        serverPort: 3055,
        pendingRequests: new Map(),
        channel: null,
      };

      // UI Elements
      const portInput = document.getElementById("port");
      const connectButton = document.getElementById("btn-connect");
      const disconnectButton = document.getElementById("btn-disconnect");
      const connectionStatus = document.getElementById("connection-status");

      // Tabs
      const tabs = document.querySelectorAll(".tab");
      const tabContents = document.querySelectorAll(".tab-content");

      // Add UI elements for progress tracking
      const progressContainer = document.getElementById("progress-container");
      const progressBar = document.getElementById("progress-bar");
      const progressMessage = document.getElementById("progress-message");
      const progressStatus = document.getElementById("progress-status");
      const progressPercentage = document.getElementById("progress-percentage");

      // Initialize UI
      function updateConnectionStatus(isConnected, message) {
        state.connected = isConnected;
        connectionStatus.innerHTML =
          message ||
          (isConnected
            ? "Connected to Cursor MCP server"
            : "Not connected to Cursor MCP server");
        connectionStatus.className = `status ${
          isConnected ? "connected" : "disconnected"
        }`;

        connectButton.disabled = isConnected;
        disconnectButton.disabled = !isConnected;
        portInput.disabled = isConnected;
      }

      // Connect to WebSocket server
      async function connectToServer(port) {
        try {
          if (state.connected && state.socket) {
            updateConnectionStatus(true, "Already connected to server");
            return;
          }

          state.serverPort = port;
          state.socket = new WebSocket(`ws://localhost:${port}`);

          state.socket.onopen = () => {
            // Generate random channel name
            const channelName = generateChannelName();
            console.log("Joining channel:", channelName);
            state.channel = channelName;

            // Join the channel using the same format as App.tsx
            state.socket.send(
              JSON.stringify({
                type: "join",
                channel: channelName.trim(),
              })
            );
          };

          state.socket.onmessage = (event) => {
            try {
              const data = JSON.parse(event.data);
              console.log("Received message:", data);

              if (data.type === "system") {
                // Successfully joined channel
                if (data.message && data.message.result) {
                  state.connected = true;
                  const channelName = data.channel;
                  updateConnectionStatus(
                    true,
                    `Connected to server on port ${port} in channel: <strong>${channelName}</strong>`
                  );

                  // Notify the plugin code
                  parent.postMessage(
                    {
                      pluginMessage: {
                        type: "notify",
                        message: `Connected to Cursor MCP server on port ${port} in channel: ${channelName}`,
                      },
                    },
                    "*"
                  );
                }
              } else if (data.type === "error") {
                console.error("Error:", data.message);
                updateConnectionStatus(false, `Error: ${data.message}`);
                state.socket.close();
              }

              handleSocketMessage(data);
            } catch (error) {
              console.error("Error parsing message:", error);
            }
          };

          state.socket.onclose = () => {
            state.connected = false;
            state.socket = null;
            updateConnectionStatus(false, "Disconnected from server");
          };

          state.socket.onerror = (error) => {
            console.error("WebSocket error:", error);
            updateConnectionStatus(false, "Connection error");
            state.connected = false;
            state.socket = null;
          };
        } catch (error) {
          console.error("Connection error:", error);
          updateConnectionStatus(
            false,
            `Connection error: ${error.message || "Unknown error"}`
          );
        }
      }

      // Disconnect from websocket server
      function disconnectFromServer() {
        if (state.socket) {
          state.socket.close();
          state.socket = null;
          state.connected = false;
          updateConnectionStatus(false, "Disconnected from server");
        }
      }

      // Handle messages from the WebSocket
      async function handleSocketMessage(payload) {
        const data = payload.message;
        console.log("handleSocketMessage", data);

        // If it's a response to a previous request
        if (data.id && state.pendingRequests.has(data.id)) {
          const { resolve, reject } = state.pendingRequests.get(data.id);
          state.pendingRequests.delete(data.id);

          if (data.error) {
            reject(new Error(data.error));
          } else {
            resolve(data.result);
          }
          return;
        }

        // If it's a new command
        if (data.command) {
          try {
            // Send the command to the plugin code
            parent.postMessage(
              {
                pluginMessage: {
                  type: "execute-command",
                  id: data.id,
                  command: data.command,
                  params: data.params,
                },
              },
              "*"
            );
          } catch (error) {
            // Send error back to WebSocket
            sendErrorResponse(
              data.id,
              error.message || "Error executing command"
            );
          }
        }
      }

      // Send a command to the WebSocket server
      async function sendCommand(command, params) {
        return new Promise((resolve, reject) => {
          if (!state.connected || !state.socket) {
            reject(new Error("Not connected to server"));
            return;
          }

          const id = generateId();
          state.pendingRequests.set(id, { resolve, reject });

          state.socket.send(
            JSON.stringify({
              id,
              type: "message",
              channel: state.channel,
              message: {
                id,
                command,
                params,
              },
            })
          );

          // Set timeout to reject the promise after 30 seconds
          setTimeout(() => {
            if (state.pendingRequests.has(id)) {
              state.pendingRequests.delete(id);
              reject(new Error("Request timed out"));
            }
          }, 30000);
        });
      }

      // Send success response back to WebSocket
      function sendSuccessResponse(id, result) {
        if (!state.connected || !state.socket) {
          console.error("Cannot send response: socket not connected");
          return;
        }

        state.socket.send(
          JSON.stringify({
            id,
            type: "message",
            channel: state.channel,
            message: {
              id,
              result,
            },
          })
        );
      }

      // Send error response back to WebSocket
      function sendErrorResponse(id, errorMessage) {
        if (!state.connected || !state.socket) {
          console.error("Cannot send error response: socket not connected");
          return;
        }

        state.socket.send(
          JSON.stringify({
            id,
            type: "message",
            channel: state.channel,
            message: {
              id,
              error: errorMessage,
              result: {}
            },
          })
        );
      }

      // Helper to generate unique IDs
      function generateId() {
        return (
          Date.now().toString(36) + Math.random().toString(36).substr(2, 5)
        );
      }

      // Add this function after the generateId() function
      function generateChannelName() {
        const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
        let result = "";
        for (let i = 0; i < 8; i++) {
          result += characters.charAt(
            Math.floor(Math.random() * characters.length)
          );
        }
        return result;
      }

      // Tab switching
      tabs.forEach((tab) => {
        tab.addEventListener("click", () => {
          tabs.forEach((t) => t.classList.remove("active"));
          tabContents.forEach((c) => c.classList.remove("active"));

          tab.classList.add("active");
          const contentId = "content-" + tab.id.split("-")[1];
          document.getElementById(contentId).classList.add("active");
        });
      });

      // Connect to server
      connectButton.addEventListener("click", () => {
        const port = parseInt(portInput.value, 10) || 3055;
        updateConnectionStatus(false, "Connecting...");
        connectionStatus.className = "status info";
        connectToServer(port);
      });

      // Disconnect from server
      disconnectButton.addEventListener("click", () => {
        updateConnectionStatus(false, "Disconnecting...");
        connectionStatus.className = "status info";
        disconnectFromServer();
      });

      // Function to update progress UI
      function updateProgressUI(progressData) {
        // Show progress container if hidden
        progressContainer.classList.remove("hidden");
        
        // Update progress bar
        const progress = progressData.progress || 0;
        progressBar.style.width = `${progress}%`;
        progressPercentage.textContent = `${progress}%`;
        
        // Update message
        progressMessage.textContent = progressData.message || "Operation in progress";
        
        // Update status text based on operation state
        if (progressData.status === 'started') {
          progressStatus.textContent = "Started";
          progressStatus.className = "";
        } else if (progressData.status === 'in_progress') {
          progressStatus.textContent = "In Progress";
          progressStatus.className = "";
        } else if (progressData.status === 'completed') {
          progressStatus.textContent = "Completed";
          progressStatus.className = "operation-complete";
          
          // Hide progress container after 5 seconds
          setTimeout(() => {
            progressContainer.classList.add("hidden");
          }, 5000);
        } else if (progressData.status === 'error') {
          progressStatus.textContent = "Error";
          progressStatus.className = "operation-error";
        }
      }

      // Send operation progress update to server
      function sendProgressUpdateToServer(progressData) {
        if (!state.connected || !state.socket) {
          console.error("Cannot send progress update: socket not connected");
          return;
        }
        
        console.log("Sending progress update to server:", progressData);
        
        state.socket.send(
          JSON.stringify({
            id: progressData.commandId,
            type: "progress_update",
            channel: state.channel,
            message: {
              id: progressData.commandId,
              type: "progress_update",
              data: progressData
            }
          })
        );
      }
      
      // Reset progress UI
      function resetProgressUI() {
        progressContainer.classList.add("hidden");
        progressBar.style.width = "0%";
        progressMessage.textContent = "No operation in progress";
        progressStatus.textContent = "Not started";
        progressStatus.className = "";
        progressPercentage.textContent = "0%";
      }

      // Listen for messages from the plugin code
      window.onmessage = (event) => {
        const message = event.data.pluginMessage;
        if (!message) return;

        console.log("Received message from plugin:", message);

        switch (message.type) {
          case "connection-status":
            updateConnectionStatus(message.connected, message.message);
            break;
          case "auto-connect":
            connectButton.click();
            break;
          case "auto-disconnect":
            disconnectButton.click();
            break;
          case "command-result":
            // Forward the result from plugin code back to WebSocket
            sendSuccessResponse(message.id, message.result);
            break;
          case "command-error":
            // Forward the error from plugin code back to WebSocket
            sendErrorResponse(message.id, message.error);
            break;
          case "command_progress":
            // Update UI with progress information
            updateProgressUI(message);
            // Forward progress update to server
            sendProgressUpdateToServer(message);
            break;
        }
      };
    </script>
  </body>
</html>